﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Xml;
using Microsoft.SharePoint;
using System.Globalization;
using System.Collections;

namespace UE.SharePoint.ImportExport.BLL.SharePoint
{
    /// <summary>
    /// this is used to write data to sharepoint list
    /// </summary>
    public class SPListWriter : IWritter
    {
        /// <summary>
        /// 
        /// </summary>
        public IStatus Status { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public Setting Setting { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public IReader Reader { get; set; }

        /// <summary>
        /// ctor
        /// </summary>
        public SPListWriter(Setting setting, IReader reader, IStatus status)
        {
            this.Setting = setting;
            this.Status = status;
            this.Reader = reader;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="added"></param>
        /// <param name="updated"></param>
        /// <returns></returns>
        public bool Writer(DataTable added, DataTable updated=null)
        {
            bool result = true;
            if (added != null)
            {
                result = result && Process(added, ProcessType.Add);
            }

            if (updated != null)
            {
                result = result && Process(updated, ProcessType.Update);
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <param name="processType"></param>
        /// <returns></returns>
        private bool Process(DataTable data, ProcessType processType)
        {
            if (data == null)
                return false;
            
            StringBuilder sbXML = new StringBuilder();
            sbXML.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            sbXML.AppendLine("<Batch OnError=\"Continue\">");
            Column[] columns = Reader.LoadColumns().Where(e => data.Columns.Contains(e.Name)).ToArray();

            /*key is primary field,
             *value:lookup items*/
            IDictionary<SPFieldLookup, IList<LookupItem>> spLookupFields = new Dictionary<SPFieldLookup, IList<LookupItem>>();
            int index = Setting.StartRow + 1; //because array started from zero,so added 1;
            foreach (DataRow row in data.Rows)
            {
                spLookupFields.Clear();
                sbXML.AppendLine("<Method ID=\"" + index++ + "\">");
                sbXML.AppendFormat("<SetList>{0}</SetList>", Setting.SPList.ID).AppendLine();
                sbXML.AppendLine("<SetVar Name=\"ID\">New</SetVar>");
                sbXML.AppendFormat("<SetVar Name=\"Cmd\">{0}</SetVar>", "Save").AppendLine();

                //sets the common field values and constructs lookup dictionary
                for (int i = 0; i < columns.Length; i++)
                {
                    SPField spField = Setting.SPList.Fields.GetFieldByInternalName(columns[i].InternalName);
                    string value = row[columns[i].Name].ToString();

                    if (spField.Type == SPFieldType.Lookup) //constructs lookup dictionary
                    {
                        SPFieldLookup spFieldLookUp = spField as SPFieldLookup;
                        SPFieldLookup spPrimaryField = spFieldLookUp.IsDependentLookup ? Setting.SPList.Fields[new Guid(spFieldLookUp.PrimaryFieldId)] as SPFieldLookup
                                                                                       : spFieldLookUp; 

                        IList<LookupItem> spFieldLookupValues = null;
                        if (!spLookupFields.TryGetValue(spPrimaryField, out spFieldLookupValues))
                        {
                            spFieldLookupValues = new List<LookupItem>();
                            spLookupFields.Add(spPrimaryField, spFieldLookupValues);
                        }
                        spFieldLookupValues.Add(new LookupItem(spFieldLookUp.LookupField, value)); 
                    } 
                    else //set the common field value
                    {
                        switch (spField.Type)
                        {
                            case SPFieldType.DateTime:
                                value = CreateISO8601DateTime(DateTime.Parse(value));
                                break;
                            case SPFieldType.User:
                                {
                                    UserFinder userFinder = new UserFinder(spField.ParentList); 
                                    IList<SPListItem> items = userFinder.Find(new LookupItem("ID", value));
                                    value = items.Count > 0 ? items[0].ID.ToString() : "";
                                }
                                break;
                        }

                        SetVar(sbXML, columns[i].InternalName, value);
                    }
                }

                //sets the lookup field values
                foreach (var item in spLookupFields)
                {
                    SetVar(sbXML, item.Key.InternalName, GetLookupFieldValue(item.Key, item.Value.ToArray()));
                }

                sbXML.AppendLine("</Method>");
            }

            sbXML.AppendLine("</Batch>");

            Setting.SPWeb.AllowUnsafeUpdates = true;
            string batchData = sbXML.ToString();
            string result = Setting.SPWeb.ProcessBatchData(batchData);  
            ValidateResult(result);

            Setting.SPWeb.AllowUnsafeUpdates = false;

            return true;
        } 

        /// <summary>
        /// 
        /// </summary>
        /// <param name="result"></param>
        private void ValidateResult(string result)
        {
            int index = 0;
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(result);
            IEnumerator enumerator = xmlDocument.DocumentElement.SelectNodes("Result").GetEnumerator();
            while (enumerator.MoveNext())
            {
                Status.ProcessedNum++;
                XmlNode xmlNode = (XmlNode)enumerator.Current;
                string innerText = xmlNode.Attributes["Code"].InnerText;
                index = int.Parse(xmlNode.Attributes["ID"].InnerText);
                if (innerText != "0")
                {
                    string text = xmlNode.SelectSingleNode("ErrorText").InnerText;
                    string message = string.Format(Status.LogFormat, index, text);
                    Status.Log(MessageCategory.Error, message);
                    continue;
                }
                else
                {
                    Status.SucceedNum++;
                }
            } 
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static string CreateISO8601DateTime(DateTime dateTime)
        {
            return dateTime.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="primaryField"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private string GetLookupFieldValue(SPField primaryField, IList<LookupItem> lookupItems)
        {
            LookupFinder lookupFinder = new LookupFinder(primaryField.ParentList, primaryField.InternalName);
            List<SPListItem> items = lookupFinder.Find(lookupItems);
            if (items.Count > 0)
            {
                return items[0].ID.ToString();
            }

            return string.Empty;
        }

        /// <summary>
        /// constructs a setvar xml
        /// </summary>
        /// <param name="sbXML"></param>
        /// <param name="column"></param>
        /// <param name="value"></param>
        private static void SetVar(StringBuilder sbXML, string internalName, string value)
        {
            sbXML.AppendFormat("<SetVar Name=\"urn:schemas-microsoft-com:office:office#{0}\">{1}</SetVar>", internalName, value).AppendLine();
        } 
    }

    /// <summary>
    /// 
    /// </summary>
    public enum ProcessType
    {
        Add = 1,
        Update
    }

    /// <summary>
    /// 
    /// </summary>
    public class LookupItem
    {
        /// <summary>
        /// 
        /// </summary>
        public string FieldInternalName;

        /// <summary>
        /// 
        /// </summary>
        public string Value = string.Empty;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="field"></param>
        /// <param name="value"></param>
        public LookupItem(string fieldInternalName, string value)
        {
            FieldInternalName = fieldInternalName;
            Value = value;
        }
    }
}
